package com.one.blocks.rbac.security;

import com.one.blocks.rbac.constant.RbacConstant.TokenCache;
import com.one.blocks.security.authc.rememberme.TokenService;
import com.one.blocks.security.config.properties.SecurityProperties;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author <a href="mailto:idler41@163.con">linfuxin</a> created on 2023-10-19 11:07:40
 */
@Component
public class RbacTokenService implements TokenService, InitializingBean {

    @Autowired
    private SecurityProperties securityProperties;

    /**
     * 泛型为<String,String>或<Object,Object>用@Autowired
     * 其余类型必须用@Resource，否则redisTemplate注入失败
     */
    @Resource
    private RedisTemplate<String, UserDetails> redisTemplate;

    private String cookieName = "sessionId";

    private String sessionAttributeKey = "AccessToken";

    private String accessHeaderKey;

    private boolean extractCookieFromHeader = false;

    @Override
    public void afterPropertiesSet() throws Exception {
        Optional.ofNullable(securityProperties.getCookieName()).ifPresent(i -> cookieName = i);
        Optional.ofNullable(securityProperties.getSessionAttributeKey()).ifPresent(i -> sessionAttributeKey = i);
        Optional.ofNullable(securityProperties.getSessionHeaderKey()).ifPresent(i -> accessHeaderKey = i);
        extractCookieFromHeader = accessHeaderKey != null;
    }

    @Override
    public UserDetails load(HttpServletRequest request) {
        String sessionId = extractCookie(request);
        if (sessionId == null) {
            return null;
        }
        request.setAttribute(sessionAttributeKey, sessionId);
        return load(sessionId);
    }

    @Override
    public UserDetails load(String sessionId) {
        return redisTemplate.opsForValue().get(buildTokenKey(sessionId));
    }

    @Override
    public String save(UserDetails userDetails) {
        // TODO 其他方式生成token
        String sessionId = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(buildTokenKey(sessionId), userDetails, securityProperties.getSessionTimeout(), TimeUnit.SECONDS);
        return sessionId;
    }

    @Override
    public void clear(String sessionId) {
        redisTemplate.delete(sessionId);
    }

    @Override
    public void clear(HttpServletRequest request) {
        clear((String) request.getAttribute(sessionAttributeKey));
    }

    @Override
    public void refreshIfNecessary(HttpServletRequest request, UserDetails userDetails) {
        RbacUserDetails rbacUserDetails = (RbacUserDetails) userDetails;
        rbacUserDetails.setAccessTime(LocalDateTime.now());
        if (isNeedRefreshSession(rbacUserDetails)) {
            rbacUserDetails.setLoginExpireTime(rbacUserDetails.getAccessTime().plusSeconds(securityProperties.getSessionTimeout()));
            redisTemplate.opsForValue().set(
                    buildTokenKey((String) request.getAttribute(sessionAttributeKey)),
                    rbacUserDetails,
                    securityProperties.getSessionTimeout(), TimeUnit.SECONDS
            );
        }
    }

    private String extractCookie(HttpServletRequest request) {
        // 先从header中获取
        if (extractCookieFromHeader) {
            String val = request.getHeader(accessHeaderKey);
            if (val != null) {
                return val;
            }
        }

        // 再从cookie中获取
        Cookie[] cookies = request.getCookies();

        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookieName.equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    private boolean isNeedRefreshSession(RbacUserDetails rbacUserDetails) {
        return !rbacUserDetails.getAccessTime()
                .plusSeconds(securityProperties.getSessionRefreshSecond())
                .isBefore(rbacUserDetails.getLoginExpireTime());
    }

    private String buildTokenKey(String sessionId) {
        return TokenCache.PREFIX + sessionId;
    }
}
